Ontdek geavanceerde JavaScript Proxy patronen voor object interceptie, validatie en dynamisch gedrag. Leer hoe je de codekwaliteit, beveiliging en onderhoudbaarheid kunt verbeteren met praktische voorbeelden.
JavaScript Proxy Patterns: Geavanceerde Object Interceptie en Validatie
Het JavaScript Proxy object is een krachtige functie waarmee u fundamentele objectbewerkingen kunt onderscheppen en aanpassen. Het maakt geavanceerde metaprogrammeringstechnieken mogelijk en biedt meer controle over objectgedrag, waardoor mogelijkheden ontstaan voor geavanceerde ontwerppatronen. Dit artikel onderzoekt verschillende Proxy-patronen en toont hun gebruik in validatie, interceptie en dynamische gedragsmodificatie. We zullen duiken in praktische voorbeelden om aan te tonen hoe Proxies de codekwaliteit, beveiliging en onderhoudbaarheid in uw JavaScript-projecten kunnen verbeteren.
De JavaScript Proxy begrijpen
In de kern wrapt een Proxy-object een ander object (het target) en onderschept bewerkingen die op dat target worden uitgevoerd. Deze onderscheppingen worden afgehandeld door traps, wat methoden zijn die aangepast gedrag definiëren voor specifieke bewerkingen, zoals het ophalen van een eigenschap, het instellen van een eigenschap of het aanroepen van een functie. De Proxy API biedt een flexibel en uitbreidbaar mechanisme om het standaardgedrag van objecten te wijzigen.
Belangrijkste concepten
- Target: Het originele object dat de Proxy wrapt.
- Handler: Een object dat de trap-methoden bevat. Elke trap komt overeen met een specifieke bewerking.
- Traps: Methoden binnen de handler die objectbewerkingen onderscheppen en aanpassen. Veelvoorkomende traps zijn onder andere
get,set,applyenconstruct.
Een Proxy creëren
Om een Proxy te creëren, gebruikt u de Proxy-constructor en geeft u het target-object en het handler-object als argumenten:
const target = {};
const handler = {
get: function(target, property, receiver) {
console.log(`Eigenschap ophalen: ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "John"; // Logt: Eigenschap ophalen: name
console.log(proxy.name); // Logt: Eigenschap ophalen: name, dan John
Veelvoorkomende Proxy Traps
Proxies bieden een reeks traps om verschillende bewerkingen te onderscheppen. Hier zijn enkele van de meest gebruikte traps:
get(target, property, receiver): Onderschept toegang tot een eigenschap.set(target, property, value, receiver): Onderschept toewijzing van een eigenschap.has(target, property): Onderschept deinoperator.deleteProperty(target, property): Onderschept dedeleteoperator.apply(target, thisArg, argumentsList): Onderschept functieaanroepen.construct(target, argumentsList, newTarget): Onderschept denewoperator.getPrototypeOf(target): Onderschept deObject.getPrototypeOf()methode.setPrototypeOf(target, prototype): Onderschept deObject.setPrototypeOf()methode.isExtensible(target): Onderschept deObject.isExtensible()methode.preventExtensions(target): Onderschept deObject.preventExtensions()methode.getOwnPropertyDescriptor(target, property): Onderschept deObject.getOwnPropertyDescriptor()methode.defineProperty(target, property, descriptor): Onderschept deObject.defineProperty()methode.ownKeys(target): Onderschept deObject.getOwnPropertyNames()enObject.getOwnPropertySymbols()methoden.
Proxy-patronen
Laten we nu enkele praktische Proxy-patronen en hun toepassingen bekijken:
1. Validatie Proxy
Een Validatie Proxy legt beperkingen op voor het toewijzen van eigenschappen. Het onderschept de set trap om de nieuwe waarde te valideren voordat de toewijzing kan worden voortgezet.
Voorbeeld: Het valideren van gebruikersinvoer in een formulier.
const user = {};
const validator = {
set: function(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 120) {
throw new Error('Ongeldige leeftijd. Leeftijd moet een geheel getal zijn tussen 0 en 120.');
}
}
target[property] = value;
return true; // Geeft succes aan
}
};
const proxy = new Proxy(user, validator);
proxy.name = 'Alice';
proxy.age = 30;
console.log(user);
try {
proxy.age = 'ongeldig'; // Gooit een fout
} catch (error) {
console.error(error.message);
}
In dit voorbeeld controleert de set trap of de age eigenschap een geheel getal is tussen 0 en 120. Als de validatie mislukt, wordt een fout gegooid, waardoor wordt voorkomen dat de ongeldige waarde wordt toegewezen.
Globaal voorbeeld: Dit validatiepatroon is essentieel voor het waarborgen van de gegevensintegriteit in globale applicaties waar gebruikersinvoer afkomstig kan zijn van diverse bronnen en culturen. Zo kan de validatie van postcodes aanzienlijk verschillen tussen landen. Een validatie proxy kan worden aangepast om verschillende validatieregels te ondersteunen op basis van de locatie van de gebruiker.
const address = {};
const addressValidator = {
set: function(target, property, value) {
if (property === 'postalCode') {
// Voorbeeld: Ervan uitgaande van een simpele Amerikaanse postcodevalidatie
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)) {
throw new Error('Ongeldige Amerikaanse postcode.');
}
}
target[property] = value;
return true;
}
};
const addressProxy = new Proxy(address, addressValidator);
addressProxy.postalCode = "12345-6789"; // Geldig
try {
addressProxy.postalCode = "abcde"; // Ongeldig
} catch(e) {
console.log(e);
}
// Voor een meer internationale applicatie zou je een meer geavanceerde validatiebibliotheek gebruiken
// die postcodes kan valideren op basis van het land van de gebruiker.
2. Logging Proxy
Een Logging Proxy onderschept toegang tot eigenschappen en toewijzing om deze bewerkingen te loggen. Het is handig voor debugging en auditing.
Voorbeeld: Het loggen van toegang tot eigenschappen en wijzigingen.
const data = {
value: 10
};
const logger = {
get: function(target, property) {
console.log(`Eigenschap ophalen: ${property}`);
return target[property];
},
set: function(target, property, value) {
console.log(`Eigenschap instellen: ${property} op ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(data, logger);
console.log(proxy.value); // Logt: Eigenschap ophalen: value, dan 10
proxy.value = 20; // Logt: Eigenschap instellen: value op 20
De get en set traps loggen de eigenschap die wordt benaderd of gewijzigd, waardoor een trace ontstaat van objectinteracties.
Globaal voorbeeld: In een multinational kunnen logging proxies worden gebruikt om de toegang tot gegevens en wijzigingen te controleren die door werknemers op verschillende locaties worden uitgevoerd. Dit is cruciaal voor naleving en beveiligingsdoeleinden. Tijdzones moeten mogelijk worden meegenomen in de logging-informatie.
const employeeData = {
name: "John Doe",
salary: 50000
};
const auditLogger = {
get: function(target, property) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [GET] Toegang tot eigenschap: ${property}`);
return target[property];
},
set: function(target, property, value) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [SET] Eigenschap instellen: ${property} op ${value}`);
target[property] = value;
return true;
}
};
const proxiedEmployee = new Proxy(employeeData, auditLogger);
proxiedEmployee.name; // Logt tijdstempel en toegang tot 'name'
proxiedEmployee.salary = 60000; // Logt tijdstempel en wijziging van 'salary'
3. Read-Only Proxy
Een Read-Only Proxy voorkomt het toewijzen van eigenschappen. Het onderschept de set trap en gooit een fout als er een poging wordt gedaan om een eigenschap te wijzigen.
Voorbeeld: Een object onveranderlijk maken.
const config = {
apiUrl: 'https://api.example.com'
};
const readOnly = {
set: function(target, property, value) {
throw new Error(`Kan eigenschap niet instellen: ${property}. Object is alleen-lezen.`);
}
};
const proxy = new Proxy(config, readOnly);
console.log(proxy.apiUrl);
try {
proxy.apiUrl = 'https://newapi.example.com'; // Gooit een fout
} catch (error) {
console.error(error.message);
}
Elke poging om een eigenschap in te stellen op de proxy resulteert in een fout, waardoor wordt gewaarborgd dat het object onveranderlijk blijft.
Globaal voorbeeld: Dit patroon is handig voor het beschermen van configuratiebestanden die niet tijdens runtime mogen worden gewijzigd, vooral in globaal gedistribueerde applicaties. Het per ongeluk wijzigen van de configuratie in één regio kan het hele systeem beïnvloeden.
const globalSettings = {
defaultLanguage: "en",
currency: "USD",
timeZone: "UTC"
};
const immutableHandler = {
set: function(target, property, value) {
throw new Error(`Kan alleen-lezen eigenschap niet wijzigen: ${property}`);
}
};
const immutableSettings = new Proxy(globalSettings, immutableHandler);
console.log(immutableSettings.defaultLanguage); // geeft 'en' weer
// Een poging om een waarde te wijzigen zal een fout gooien
// immutableSettings.defaultLanguage = "fr"; // gooit Error: Kan alleen-lezen eigenschap niet wijzigen: defaultLanguage
4. Virtual Proxy
Een Virtual Proxy controleert de toegang tot een resource waarvan het maken of ophalen duur kan zijn. Het kan het maken van de resource uitstellen totdat deze daadwerkelijk nodig is.
Voorbeeld: Lazy loading van een afbeelding.
const image = {
display: function() {
console.log('Afbeelding weergeven');
}
};
const virtualProxy = {
get: function(target, property) {
if (property === 'display') {
console.log('Afbeelding maken...');
const realImage = {
display: function() {
console.log('Echte afbeelding weergeven');
}
};
target.display = realImage.display;
return realImage.display;
}
return target[property];
}
};
const proxy = new Proxy(image, virtualProxy);
// De afbeelding wordt pas gemaakt als display wordt aangeroepen.
proxy.display(); // Logt: Afbeelding maken..., dan Echte afbeelding weergeven
Het echte image-object wordt pas gemaakt wanneer de display-methode wordt aangeroepen, waardoor onnodig resourceverbruik wordt voorkomen.
Globaal voorbeeld: Beschouw een wereldwijde e-commerce website die afbeeldingen van producten aanbiedt. Met behulp van een Virtual Proxy kunnen afbeeldingen alleen worden geladen wanneer ze zichtbaar zijn voor de gebruiker, waardoor het bandbreedtegebruik wordt geoptimaliseerd en de paginalaadtijden worden verbeterd, vooral voor gebruikers met trage internetverbindingen in verschillende regio's.
const product = {
loadImage: function() {
console.log("Hoog-resolutie afbeelding laden...");
// Simuleer het laden van een grote afbeelding
setTimeout(() => {
console.log("Afbeelding geladen");
this.displayImage();
}, 2000);
},
displayImage: function() {
console.log("De afbeelding weergeven");
}
};
const lazyLoadProxy = {
get: function(target, property) {
if (property === "displayImage") {
// In plaats van direct te laden, stel het laden uit
console.log("Verzoek om afbeelding weer te geven ontvangen. Laden...");
target.loadImage();
return function() { /* Opzettelijk Leeg */ }; // Retourneer een lege functie om directe uitvoering te voorkomen
}
return target[property];
}
};
const proxiedProduct = new Proxy(product, lazyLoadProxy);
// Call displayImage activeert het lazy loading proces
proxiedProduct.displayImage();
5. Revocable Proxy
Met een Revocable Proxy kunt u de proxy op elk gewenst moment intrekken, waardoor deze onbruikbaar wordt. Dit is handig voor beveiligingsgevoelige scenario's waarin u de toegang tot een object moet controleren.
Voorbeeld: Tijdelijke toegang verlenen tot een resource.
const target = {
secret: 'Dit is een geheim'
};
const handler = {
get: function(target, property) {
console.log('Toegang tot geheime eigenschap');
return target[property];
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.secret); // Logt: Toegang tot geheime eigenschap, dan Dit is een geheim
revoke();
try {
console.log(proxy.secret); // Gooit een TypeError
} catch (error) {
console.error(error.message); // Logt: Kan 'get' niet uitvoeren op een proxy die is ingetrokken
}
De Proxy.revocable() methode maakt een herroepbare proxy. Door de revoke()-functie aan te roepen, wordt de proxy onbruikbaar, waardoor verdere toegang tot het target-object wordt voorkomen.
Globaal voorbeeld: In een wereldwijd gedistribueerd systeem kunt u een herroepbare proxy gebruiken om tijdelijke toegang tot gevoelige gegevens te verlenen aan een service die in een specifieke regio wordt uitgevoerd. Na een bepaalde tijd kan de proxy worden ingetrokken om ongeautoriseerde toegang te voorkomen.
const sensitiveData = {
apiKey: "SUPER_SECRET_KEY"
};
const handler = {
get: function(target, property) {
console.log("Toegang tot gevoelige gegevens");
return target[property];
}
};
const { proxy: dataProxy, revoke: revokeAccess } = Proxy.revocable(sensitiveData, handler);
// Toegang toestaan gedurende 5 seconden
setTimeout(() => {
revokeAccess();
console.log("Toegang ingetrokken");
}, 5000);
// Poging om toegang te krijgen tot gegevens
console.log(dataProxy.apiKey); // Logt de API Key
// Na 5 seconden zal dit een fout gooien
setTimeout(() => {
try {
console.log(dataProxy.apiKey); // Gooit: TypeError: Kan 'get' niet uitvoeren op een proxy die is ingetrokken
} catch (error) {
console.error(error);
}
}, 6000);
6. Type Conversie Proxy
Een Type Conversie Proxy onderschept toegang tot eigenschappen om de geretourneerde waarde automatisch om te zetten in een specifiek type. Dit kan handig zijn voor het werken met gegevens uit verschillende bronnen die mogelijk inconsistente typen hebben.
Voorbeeld: Stringwaarden converteren naar getallen.
const data = {
price: '10.99',
quantity: '5'
};
const typeConverter = {
get: function(target, property) {
const value = target[property];
if (typeof value === 'string' && !isNaN(Number(value))) {
return Number(value);
}
return value;
}
};
const proxy = new Proxy(data, typeConverter);
console.log(proxy.price + 1); // Logt: 11.99 (nummer)
console.log(proxy.quantity * 2); // Logt: 10 (nummer)
De get trap controleert of de eigenschapswaarde een string is die kan worden geconverteerd naar een getal. Zo ja, dan zet deze de waarde om in een getal voordat deze wordt geretourneerd.
Globaal voorbeeld: Bij het omgaan met gegevens afkomstig van API's met verschillende formatteringsconventies (bijvoorbeeld verschillende datumnotaties of valutasymbolen), kan een Type Conversie Proxy zorgen voor gegevensconsistentie in uw applicatie, ongeacht de bron. Bijvoorbeeld, het afhandelen van verschillende datumnotaties en ze allemaal converteren naar ISO 8601-formaat.
const apiData = {
dateUS: "12/31/2023",
dateEU: "31/12/2023"
};
const dateFormatConverter = {
get: function(target, property) {
let value = target[property];
if (property.startsWith("date")) {
// Probeer zowel Amerikaanse als Europese datumnotaties om te zetten in ISO 8601
if (property === "dateUS") {
const [month, day, year] = value.split("/");
value = `${year}-${month}-${day}`;
} else if (property === "dateEU") {
const [day, month, year] = value.split("/");
value = `${year}-${month}-${day}`;
}
return value;
}
return value;
}
};
const proxiedApiData = new Proxy(apiData, dateFormatConverter);
console.log(proxiedApiData.dateUS); // Geeft: 2023-12-31
console.log(proxiedApiData.dateEU); // Geeft: 2023-12-31
Beste praktijken voor het gebruik van Proxies
- Gebruik Proxies oordeelkundig: Proxies kunnen complexiteit aan uw code toevoegen. Gebruik ze alleen als ze aanzienlijke voordelen bieden, zoals verbeterde validatie, logging of controle over het objectgedrag.
- Overweeg prestaties: Proxy traps kunnen overhead introduceren. Profiteer uw code om ervoor te zorgen dat Proxies de prestaties niet negatief beïnvloeden, vooral in prestatiegevoelige secties.
- Fouten netjes afhandelen: Zorg ervoor dat uw trap-methoden fouten op de juiste manier afhandelen en indien nodig informatieve foutmeldingen geven.
- Gebruik Reflect API: De
ReflectAPI biedt methoden die het standaardgedrag van objectbewerkingen weerspiegelen. GebruikReflect-methoden binnen uw trap-methoden om waar nodig te delegeren aan het originele gedrag. Dit zorgt ervoor dat uw traps de bestaande functionaliteit niet verbreken. - Documenteer uw Proxies: Documenteer duidelijk het doel en het gedrag van uw Proxies, inclusief de traps die worden gebruikt en de beperkingen die worden afgedwongen. Dit helpt andere ontwikkelaars om uw code te begrijpen en te onderhouden.
Conclusie
JavaScript Proxies zijn een krachtige tool voor geavanceerde objectmanipulatie en interceptie. Door verschillende Proxy-patronen te begrijpen en toe te passen, kunt u de codekwaliteit, beveiliging en onderhoudbaarheid verbeteren. Van het valideren van gebruikersinvoer tot het controleren van de toegang tot gevoelige resources, Proxies bieden een flexibel en uitbreidbaar mechanisme voor het aanpassen van het objectgedrag. Wanneer u de mogelijkheden van Proxies verkent, vergeet dan niet om ze oordeelkundig te gebruiken en uw code grondig te documenteren.
De voorbeelden laten zien hoe u JavaScript Proxies kunt gebruiken om problemen in de echte wereld op te lossen in een globale context. Door deze patronen te begrijpen en toe te passen, kunt u robuustere, veiligere en beter onderhoudbare applicaties maken die voldoen aan de behoeften van een diverse gebruikersbasis. Vergeet niet om altijd de globale implicaties van uw code te overwegen en uw oplossingen aan te passen aan de specifieke vereisten van verschillende regio's en culturen.